算法笔记(2) —— 指针

一、指针基本概念

1、指针自身的类型

从语法的角度看,只要把指针声明语句里的指针变量名去掉,剩下的部分就是该指针的类型。

1
2
3
4
int *ptr;          //指针的类型是int*
int **ptr; //指针的类型是int**
int *ptr[3]; //指针的类型是int*[3]
int (*ptr)[3]; //指针的类型是int(*)[3]

2、指针指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,只要把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的部分就是指针所指向的类型。

1
2
3
4
int *ptr;          //指针所指向的类型是int
int **ptr; //指针所指向的的类型是int*
int *ptr[3]; //指针所指向的类型是int[3]
int (*ptr)[3]; //指针所指向的的类型是int()[3]

*p[N], (*P)[N],及**p的区别:

  • int *p[N] 表示定义一个指针数组,也就是说定义了N个不同指向 int 型的指针。
1
2
int a, b, c, d;
int *p[] = {&a, &b, &c, &d};
  • int (*p)[N] 表示定义一个数组指针,指向一个 int [N] 型数组的指针。
1
2
3
int a[2][3] = {1, 2, 3, 4, 5, 6};
int (*p)[3] = a;
printf("(*(p+1))[1] = a[1][1] = %d\n", (*(p+1))[1]);
  • int **p 表示定义一个指向指针的指针。

3、指针的值(指针所指向的内存区或地址)

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为 sizeof(指针所指向的类型) 的一片内存区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main() {
int a = 233;
int *p;
p = &a;

printf(" &a = %d\n", &a); // &a = 6487572
printf(" p = %d\n", p); // p = 6487572
printf("p+1 = %d\n", p+1); //p+1 = 6487576
printf("sizeof *p = %d\n", sizeof(*p)); //sizeof *p = 4
printf("sizeof int = %d\n", sizeof(int)); //sizeof int = 4

return 0;
}

二、运算符&和*

& 是取地址运算符,* 是间接运算符
&a 的运算结果是一个指针,该指针的值是声明 a 时开辟的地址,指针的类型是 a 的类型对应的指针类型,指针所指向的类型就是 a 的类型,指针所指向的地址就是 a 的地址。
*p 的运算结果是 p 所指向的东西,类型是 p 指向的类型,所占用的地址是 p 所指向的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
int main() {
int a = 12;
int b = 21;
int *p; //p是一个指针,该指针的自身的类型为int *型,指向的类型为int型。
int **ptr; //ptr是一个二级指针(指向指针的指针),该指针自身的类型为int **型,指向的类型为int *型。

p = &a; //&a的结果是一个指针,该指针的类型是int *型,指向的类型是int型,指向的地址是a的地址;
//将&a赋值给p,即将变量a所在内存地址存放到p中。
*p = 24; //*p用于取出p中存放的内存地址(变量a的地址)所对应的内容(变量a的值);
//将24赋值给*p,即将24赋值给变量a。
ptr = &p; //&p的运算结果是一个指针,该指针的类型为int **型;
//将&p赋值给ptr,就是将指针p自身所在内存地址存放在到ptr中。
printf(" ptr = &p :\n");
printf(" ptr = %d\n", ptr); // ptr = 6487560
printf(" *ptr = %d\n", *ptr); // *ptr = 6487572
printf(" **ptr = %d\n", **ptr); //**ptr = 24
printf(" &p = %d\n", &p); // &p = 6487560
printf(" p = %d\n", p); // p = 6487572
printf(" *p = %d\n", *p); // *p = 24
printf(" &a = %d\n", &a); // &a = 6487572
printf(" a = %d\n", a); // a = 24
printf(" &b = %d\n", &b); // &b = 6487568
printf(" b = %d\n", b); // b = 21

*ptr = &b; //*ptr用于取出ptr中存放的内存地址(指针p自身所在的地址)所对应的内容(指针p);
//*ptr是一个指针,&b的结果也是一个指针,这两个指针的类型和所指向的类型是一样的;
//将&b赋值给*ptr,就是将变量b的地址存放到指针p中。
printf(" *ptr = &b :\n");
printf(" ptr = %d\n", ptr); // ptr = 6487560
printf(" *ptr = %d\n", *ptr); // *ptr = 6487568
printf(" **ptr = %d\n", **ptr); //**ptr = 21
printf(" &p = %d\n", &p); // &p = 6487560
printf(" p = %d\n", p); // p = 6487568
printf(" *p = %d\n", *p); // *p = 21
printf(" &a = %d\n", &a); // &a = 6487572
printf(" a = %d\n", a); // a = 24
printf(" &b = %d\n", &b); // &b = 6487568
printf(" b = %d\n", b); // b = 21

**ptr = 34; //**ptr先取出ptr中存放的地址所对应的内容(指针p),再对取出的内容进行*运算操作,即*p;
//将34赋值给**ptr,就是将34赋值给*p,也就是将34赋值给变量b。
printf("**ptr = 34 :\n");
printf(" ptr = %d\n", ptr); // ptr = 6487560
printf(" *ptr = %d\n", *ptr); // *ptr = 6487568
printf(" **ptr = %d\n", **ptr); //**ptr = 34
printf(" &p = %d\n", &p); // &p = 6487560
printf(" p = %d\n", p); // p = 6487568
printf(" *p = %d\n", *p); // *p = 34
printf(" &a = %d\n", &a); // &a = 6487572
printf(" a = %d\n", a); // a = 24
printf(" &b = %d\n", &b); // &b = 6487568
printf(" b = %d\n", b); // b = 34

return 0;
}

三、数组与指针

重点补充:

  1. 指针的加减,以指针所指向的类型为单位进行偏移。
  2. 首地址:一段内存空间中的第一个存储单元的地址,其类型为该存储单元类型对应的指针类型。

1、一维数组与指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main() {
int a[5] = {0};

printf(" a = %d\n", a); // a = 6487552 a表示数组的首地址,并指向存储单元a[0],其自身类型为int*型;
printf(" a+1 = %d\n", a+1); // a+1 = 6487556 a+1偏移与指针所指向的类型(int型)有关,为4字节。
printf(" &a = %d\n", &a); // &a = 6487552 &a是一个指针,指向整个数组,其自身类型为int(*)[5]型;
printf("&a+1 = %d\n", &a+1); //&a+1 = 6487572 &a+1偏移与指针所指向的类型(int[5]型)有关,为20字节;

printf("sizeof int = %d\n", sizeof(int)); //sizeof int = 4
printf("sizeof int[5] = %d\n", sizeof(int[5])); //sizeof int[5] = 20

return 0;
}

2、二维数组与指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
int main() {
int a[3][4] = {0};

printf(" &a = %d\n", &a); // &a = 6487520 &a为指向整个二维数组的指针,其自身类型为int(*)[3][4]型;
printf(" &a+1 = %d\n", &a+1); // &a+1 = 6487568 &a+1偏移与指针指向的类型(int[3][4]型)有关,为48字节。
printf(" a = %d\n", a); // a = 6487520 a表示二维数组的首地址,指向一维数组a[0],类型为int*[4]型;
printf(" a+1 = %d\n", a+1); // a+1 = 6487536 a+1便宜与指针指向的类型(int[4]型)有关,为16字节。
printf(" a[0] = %d\n", a[0]); // a[0] = 6487520 a[0]表示一维数组的首地址,指向元素a[0][0],类型为int*型;
printf("a[0]+1 = %d\n", a[0]+1); //a[0]+1 = 6487524 a[0]+1偏移与指针指向的类型(int)有关,为4字节。

printf("sizeof int[3][4] = %d\n", sizeof(int[3][4])); //sizeof int[3][4] = 48
printf("sizeof int[4] = %d\n", sizeof(int[4])); //sizeof int[4] = 16
printf("sizeof int = %d\n", sizeof(int)); //sizeof int = 4

return 0;
}

对于三维数组 int a[1][2][3],有:

  1. 定位到数组元素 a[x][y][z] :*(*(*(a+x)+y)+z)
  2. 指针(数组名)与自身类型、指向类型以及单位偏移量的关系表
指针或数组名 自身类型 指向类型 单位偏移量
&a int(*)[1][2][3] int[1][2][3] sizeof(int[1][2][3]) $ = 1×2×3×4 = 24$字节
a int(*)[2][3] int[2][3] sizeof(int[2][3]) $ = 2×3×4 = 24$字节
a[0] int(*)[3] int[3] sizeof(int[3]) $ = 3×4 = 12$字节
a[0][0] int* int sizeof(int) $= 4$字节

总结:

  1. 一维数组的首地址是该一维数组的第一个数组元素的地址;二维数组的首地址是该二维数组的第一个一维数组的地址。
  2. a 和 &a 的结果都是数组的首地址,但他们的类型是不一样的:a—— 数据类型 *&a—— 数据类型 (*) [数组元素个数]
  3. 数组名仅仅是“相当于”指针,而并不是真的是指针,数组名仅仅只是一个常量(一个值为数组首元素地址的常量),所以不能进行加减运算,而常量更是无法取地址的,之所以有 &a,是因为这里的 a 此时代表了整个数组

四、结构体与指针

访问结构体内的元素有两种方法:“ . ”操作和“ -> ”操作。现将 studentInfo 型定义为如下形式:

1
2
3
4
5
struct studentInfo {
int id;
char name[20];
studentInfo *next;
}stu, *p;

此时结构体变量中定义了普通变量 stu 和指针变量 p

于是访问 stu 中变量的写法如下:

1
2
3
stu.id
stu.name
stu.next

而访问指针变量 p 中元素的写法有两种,如下所示:

1
2
3
(*p).id      或     p -> id
(*p).name 或 p -> name
(*p).next 或 p -> next

五、函数与指针

指针类型可以作为函数参数的类型,这时视为把变量的地址传入函数。如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地被改变,这种传递方式被称为地址传递

由于函数在接收参数的过程中是单向一次性的值传递,即将实参传输给对应的形参,这样相当于产生了一个副本,对这个副本的操作不会影响实参的值,只有在获取实参地址的情况下对形参进行操作,才能真正地修改实参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<stdio.h>

void swap_correct(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}

void swap_error1(int x, int y) { //当swap函数被调用时,为形参x和y(int型)分配存储空间,
int temp = x; //并将实参的值传入,函数执行过程中,将形参x和y的值进行交换,
x = y; //当函数执行结束之后,形参x和y所占用的存储空间就会被释放,
y = temp; //这种传递的方式,并不会对实参的值产生影响,此即为值传递。
}

void swap_error2(int *x, int *y) { //main函数传给swap函数的地址其实是一个无符号整形的数,其本身和普通
int *temp = x; //变量一样只是值传递,函数对地址的修改无法改变main函数中的实参的值,
x = y; //必须对传入的地址所指向的数据进行修改才能改变main函数中的实参的值。
y = temp;
}

int main() {
int a = 1, b = 2, c = 1, d = 2, e = 1, f = 2;
int *p1 = &c, *p2 = &d, *p3 = &e, *p4 = &f;

swap_error1(a, b);
printf("a = %d, b = %d\n", a, b); //a = 1, b = 2

swap_error2(p1, p2);
printf("c = %d, d = %d\n", *p1, *p2); //c = 1, d = 2

swap_correct(p3, p4);
printf("e = %d, f = %d\n", *p3, *p4); //e = 2, f = 1

return 0;
}
-------------本文已经结束 ~\(≧▽≦)/~ 感谢您的阅读-------------